package br.com.carser.dao.infra;

import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;

import org.hibernate.Criteria;
import org.hibernate.Session;

import br.com.carser.exception.DuplicateEntityException;
import br.com.carser.exception.ParentEntityException;

/**
 * Abstract class to implements methods of BaseSession. TODO: Logger
 * 
 * @author Eduardo T. Serete. May 25, 2011. 1:18:12 AM
 */
public abstract class BaseDAO<T extends BaseEntity> {

	@PersistenceContext
	private EntityManager entityManager;

	@SuppressWarnings("unchecked")
	private Class<T> clazz = (Class<T>) ((ParameterizedType) this.getClass()
			.getGenericSuperclass()).getActualTypeArguments()[0];;

	/**
	 * Flush the session to force exit.
	 * 
	 * @throws Throwable
	 *             : Connections issues.
	 */
	public void flush() {
		entityManager.flush();
	}

	/**
	 * Save or update a entity.
	 * 
	 * @param entity
	 *            entity to save.
	 * @return entity syncronized with database.
	 * @throws DuplicateEntityException
	 *             violation constraint.
	 */
	public T saveOrUpdate(T entity) throws DuplicateEntityException {
		try {
			if (entity.getId() != null) {
				entity = entityManager.merge(entity);
			} else {
				entityManager.persist(entity);
				entityManager.refresh(entity);
			}
		} catch (PersistenceException e) {
			throw new DuplicateEntityException(e);
		}
		return entity;
	}

	/**
	 * Delete a entity.
	 * 
	 * @param entity
	 *            Entity to delete.
	 * @throws ParentEntityException
	 */
	public void delete(T entity) throws ParentEntityException {
		entity = entityManager.merge(entity);
		entityManager.remove(entity);
	}

	/**
	 * Refresh a entity.
	 * 
	 * @param entity
	 *            Entity to refresh.
	 */
	public void refresh(T entity) {
		entityManager.refresh(entity);
	}

	/**
	 * Get entity by id.
	 * 
	 * @param id
	 *            Entity id.
	 * @return entity.
	 */
	public T getById(Long id) {
		return entityManager.find(this.clazz, id);
	}

	/**
	 * Get entity with LOCKMODE of type READ (Optmistic lock).
	 * 
	 * @param id
	 *            Entity id.
	 * @return entity.
	 */
	public T getByIdForUpdate(Long id) {
		return entityManager.find(this.clazz, id, LockModeType.READ);
	}

	/**
	 * Get entity with LOCKMODE of type READ (Optmistic lock).
	 * 
	 * @param id
	 *            Entity id.
	 * @return entity.
	 */
	@SuppressWarnings("unchecked")
	public List<T> getAll() {
		return (List<T>) getHibernateCriteria().list();
	}

	/**
	 * Get the entity manager.
	 * 
	 * @return entity.
	 */
	protected EntityManager getEntityManager() {
		return entityManager;
	}

	/**
	 * Get the hibernate session.
	 * 
	 * @return session.
	 */
	protected Session getHibernateSession() {
		return entityManager.unwrap(Session.class);
	}

	/**
	 * Get the hibernate criteria.
	 * 
	 * @return criteria.
	 */
	protected Criteria getHibernateCriteria() {
		return getHibernateSession().createCriteria(clazz);
	}
}
